#pragma once

#include "Log.hpp"
#include <iostream>
#include <string>
#include <unordered_map>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

class Server
{
public:
    // 服务端构造函数
    Server(uint16_t port, const std::string& ip = "")
        : _port(port)
        , _ip(ip)
        , _sock(-1)
    { }

    // 初始化服务器
    void init()
    {
        // 1. 创建网络套接字
        _sock = socket(AF_INET, SOCK_DGRAM, 0);   
        if(_sock < 0)  // 检查套接字创建成功与否
        {
            logMessage(FATAL, "%d:%s\n", errno, strerror(errno));
            exit(2);
        }
        logMessage(DEBUG, "套接字创建成功, _sock:%d\n", _sock);

        // 2. bind:将用户设置的ip和port在内核中与进程相关联
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        // INADDR_ANY: 表示服务器在工作过程中可以从任意ip获取数据
        // inet_addr函数: 将主机ip转为4字节网络ip
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());   
        local.sin_port = htons(_port);   // 将主机端口转换为网络端口格式

        if(bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "%d:%s\n", errno, strerror(errno));
            exit(3);
        }
        logMessage(DEBUG, "用户设置的ip和port在内核中与进程相关联成功！\n");
    }

    // 启动服务器程序
    void start()
    {
        // 服务器进程永不退出
        // 从客户端读取数据
        char buffer[1024];    // 输出缓冲区
        char key[128];         // 存储客户端ip和端口号
        struct sockaddr_in sock_cli;             // 客户端套接字
        memset(&sock_cli, 0, sizeof(sock_cli));  // 初始化0
        socklen_t len = sizeof(sock_cli);        // 输入型参数 -- 套接字长度

        std::string addr_cli;   // 数据源客户端的ip
        uint16_t port_cli;      // 数据源客户端的端口号

        while(true)
        {
            // 输出读取到的数据
            memset(buffer, 0, sizeof(buffer));
            memset(key, 0, sizeof(key));

            ssize_t n = recvfrom(_sock, (void *)buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&sock_cli, &len);

            if(n > 0)
            {
                buffer[n] = 0;   // 添加尾部'/0'
                addr_cli = inet_ntoa(sock_cli.sin_addr);  // inet_ntoa函数负责将网络ip转换为主机ip
                port_cli = ntohs(sock_cli.sin_port);  // 网络套接字转换为主机套接字
                snprintf(key, 128, "[%s-%d]", addr_cli.c_str(), port_cli);
               
                printf("[%s:%d]# %s\n", addr_cli.c_str(), port_cli, buffer);  // 输出发送端的ip和port，以及发送的内容
            }
            else if(n == 0)
            {
                logMessage(DEBUG, "未读取到数据!\n");
                continue;
            }
            else // 数据读取失败
            {
                logMessage(ERROR, "读取数据失败!\n");
                continue;
            }

            // 将客户端的ip和port插入到哈希表
            if(_mp.find(key) == _mp.end())
            {
                _mp.insert({key, sock_cli});
                logMessage(NORMAL, "成功插入客户端, %s\n", key);
            }

            // 将读取到的数据全部发送给客户端主机
            for(const auto& iter : _mp)
            {
                std::string msg_cli;
                msg_cli += key;
                msg_cli += "# ";
                msg_cli += buffer;
                if(sendto(_sock, msg_cli.c_str(), msg_cli.size(), 0, (struct sockaddr *)&(iter.second), sizeof(iter.second)) < 0)
                {
                    logMessage(ERROR, "服务器发回消息失败!");
                    continue;
                } 
                logMessage(NORMAL, "向客户端写回数据 -- %s\n", iter.first.c_str());
            }
        }
    }

    // 析构函数
    ~Server()
    {
        if(_sock >= 0)
        {
            close(_sock);
        }
    }

private:
    uint16_t _port;    // 端口号
    std::string _ip;   // 服务器ip地址
    int _sock;         // 套接字对应文件描述符
    std::unordered_map<std::string, struct sockaddr_in> _mp; // 哈希表，记录接收到信息的客户端的ip和port 
};